﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;

namespace Useful.GUI.HierarchyDataGridView
{
    /// Заголовочная ячейка строки с поддержкой группировки
    public class RowHeaderCell : DataGridViewRowHeaderCell
    {
        internal class CollapsingEventArgs : EventArgs
        {
            public RowHeaderCell HeaderCell;
            public int GroupIndex;

            public CollapsingEventArgs(RowHeaderCell headerCell, int groupIndex)
            {
                this.HeaderCell = headerCell;
                this.GroupIndex = groupIndex;
            }
        }

        // Заголовочная ячейка создержит список групп, в которые она входит в том или ином виде
        private readonly List<GroupItem> groups = new List<GroupItem>();

        internal List<GroupItem> Groups
        {
            get
            {
                return groups;
            }
        }

        private readonly Pen _pen = new Pen(Color.Black, 2);
        //private readonly Pen _pen2 = new Pen(Color.Black, 1);

        internal event EventHandler<CollapsingEventArgs> BeforeCollapsing = delegate { };

        // Создать заголовочную ячейку-группы и добавить в список её гупп заголовочный элемент группы
        internal static RowHeaderCell AddGroupHeaderCell(List<DataGridViewRow> group)
        {
            RowHeaderCell res = new RowHeaderCell();

            res.groups.Add(GroupItem.CreateHeaderItem(group));

            return res;
        }

        // Создать заголовочную ячейку-группы и добавить в список её гупп заголовочный элемент группы
        internal static RowHeaderCell AddGroupHeaderCell(DataGridViewRowHeaderCell headerCell, List<DataGridViewRow> group, EventHandler<CollapsingEventArgs> BeforeCollapsing, out GroupItem groupItem)
        {
            RowHeaderCell res = null;

            if (headerCell is RowHeaderCell)
                res = (RowHeaderCell)headerCell;
            else
                res = new RowHeaderCell();

            groupItem = GroupItem.CreateHeaderItem(group);
            res.groups.Add(groupItem);
            res.BeforeCollapsing = BeforeCollapsing;

            return res;
        }

        // Создать обычную ячейку группы и добавить в спиок её групп заголовочный элемент группы
        internal static RowHeaderCell AddGroupItemCell(DataGridViewRowHeaderCell headerCell, GroupItem baseGroup)
        {
            RowHeaderCell res = null;

            if (headerCell is RowHeaderCell)
                res = (RowHeaderCell)headerCell;
            else
                res = new RowHeaderCell();

            res.groups.Add(GroupItem.CreateGroupItem(baseGroup));

            return res;
        }

        // Пометить ячейку последней в группе
        internal static RowHeaderCell SetLastGroupHeaderCell(DataGridViewRowHeaderCell headerCell, bool isLast)
        {
            RowHeaderCell res = null;

            if (headerCell is RowHeaderCell)
                res = (RowHeaderCell)headerCell;
            else
                throw new GuiException("SetLastGroupHeaderCell");

            res.groups[res.groups.Count - 1].SetLastGroup(isLast);

            return res;
        }

        // Обработка сворачивания/разворачивания группы
        protected override void OnMouseClick(DataGridViewCellMouseEventArgs e)
        {
            if (groups.Count != 0 && e.Button == MouseButtons.Left && e.ColumnIndex == -1)
            {
                int? groupIndex = GetGroupIndex(e);

                if (groupIndex != null)
                {
                    GroupItem group = groups[groupIndex.Value];

                    if (group.IsHeaderItem)
                    {
                        BeforeCollapsing(this, new CollapsingEventArgs((RowHeaderCell)this.DataGridView.Rows[e.RowIndex].HeaderCell, groupIndex.Value));

                        CollapseGroup(true, groupIndex.Value, !group.IsExpanded);
                    }
                }
            }

            base.OnMouseClick(e);
        }

        public void Collapse(int groupIndex, bool visible)
        {
            if (groups.Count > groupIndex)
            {
                GroupItem group = groups[groupIndex];

                if (group.IsHeaderItem)
                {
                    //BeforeCollapsing(this, new CollapsingEventArgs((RowHeaderCell)this.DataGridView.Rows[RowIndex].HeaderCell, groupIndex));

                    CollapseGroup(true, groupIndex, visible);
                }
            }
        }

        // Определяет попадает ли точка в группу
        private bool TestPointInGroupRect(int groupIndex, Point p)
        {
            bool res = false;

            float x = 0 + 7 + groupIndex * 15;
            float y = 0;
            float h = Size.Height;

            RectangleF rect = new RectangleF(x - 4, y + h - 14, 13, 13);

            if (rect.Contains(p))
                res = true;            

            return res;
        }

        // Определяет группу по точке
        private int? GetGroupIndex(DataGridViewCellMouseEventArgs e)
        {
            int? res = null;

            for (int i = 0; i < groups.Count; i++)
            {
                if (TestPointInGroupRect(i, e.Location))
                {
                    res = i;
                    break;
                }
            }

            return res;
        }

        // Сворачивание/ разворачивание группы
        private void CollapseGroup(bool needSuspend, int groupIndex, bool visible)
        {
            if (needSuspend)
                base.DataGridView.SuspendLayout();

            GroupItem group = groups[groupIndex];
            group.Visible = visible;

            if (group.IsHeaderItem)
            {
                for (int i = 1; i < group.Count; i++) // По всем строкам в группе
                {
                    DataGridViewRow row = group.GetRow(i);

                    row.Visible = visible && GetRowVisibleInSubGroups(row, groupIndex + 1); // Учитывем видивость в группе-потомке
                }

                if (visible && groupIndex > 0) // Обраблотка ситуации, когда нужно раскрыть родительскую группу
                {
                    CollapseGroup(needSuspend, groupIndex - 1, visible);
                }

                if (group.IsHeaderItem)
                {
                    group.IsExpanded = visible;
                }
            }

            if (needSuspend)
                base.DataGridView.ResumeLayout();
        }

        private static bool GetRowVisibleInSubGroups(DataGridViewRow row, int groupIndex)
        {
            bool res = true;

            RowHeaderCell h = (RowHeaderCell)row.HeaderCell;

            // Если это первая строка (заголовок) в подгруппе - всегда её показывает иначе не увилим квадратика
            if (groupIndex < h.Groups.Count)
            {
                //res = h.Groups[groupIndex].Visible || h.Groups[groupIndex].IsHeaderItem; // Если это первая строка (заголовок) в подгруппе - всегда её показывает иначе не увилим квадратика

                res = (h.Groups[groupIndex].Visible || h.Groups[groupIndex].IsHeaderItem) && GetRowVisibleInSubGroups(row, groupIndex + 1);
            }

            return res;
        }

        protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates cellState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts)
        {
            base.Paint(graphics, clipBounds, cellBounds, rowIndex, cellState, value, formattedValue, errorText, cellStyle, advancedBorderStyle, paintParts & ~DataGridViewPaintParts.ContentBackground);

            for (int i = 0; i < groups.Count; i++) // Рисуем квадратики для всех групп
                PaintGroup(graphics, cellBounds, i);
        }

        private Brush _ClearBrush = null;
        private Brush GetClearBrush()
        {
            if (_ClearBrush == null)
            {
                Color backColor = base.Style.BackColor != Color.Empty ? base.Style.BackColor : base.InheritedStyle.BackColor;

                _ClearBrush = new SolidBrush(backColor);
            }

            return _ClearBrush;
        }

        private void PaintGroup(Graphics graphics, Rectangle cellBounds, int groupIndex)
        {
            float x = cellBounds.Left + 7 + groupIndex * 16;
            float y = cellBounds.Top;
            float h = cellBounds.Height;

            //Pen pen = new Pen(_pen.Brush, _pen.Width);
            Pen pen = _pen;

            //if (groupIndex != 0)
            //    pen.Color = Color.Red;

            graphics.FillRectangle(GetClearBrush(), x - 4, y + h - 14, 13, 12); 

            switch (groups[groupIndex].RowType)
            {
                case ControllerType.Simple:
                    break;
                case ControllerType.GroupRowItem:
                    graphics.DrawLine(pen, x, y, x, y + h);
                    graphics.DrawLine(pen, x + 14, y + h / 2, x + 16, y + h / 2); // Точка
                    break;
                case ControllerType.LastGroupItem:
                    graphics.DrawLine(pen, x, y + 0, x, y + h); // Вертикальная
                    graphics.DrawLine(pen, x + 14, y + h / 2, x + 16, y + h / 2); // Точка
                    graphics.DrawLine(pen, x - 1, y - 1 + h, x + 7, y - 1 + h); // Горизонтальная
                    break;
                case ControllerType.GroupHeaderPlus:
                    graphics.DrawRectangle(_pen, x - 4, y + h - 14, 13, 13);
                    graphics.DrawLine(pen, x, y + h - 7, x + 6, y + h - 7);

                    graphics.DrawLine(pen, x + 3, y + h - 10, x + 3, y + h - 4);
                    break;
                case ControllerType.GroupHeaderMinus:
                    graphics.DrawRectangle(_pen, x - 4, y + h - 14, 13, 13);
                    graphics.DrawLine(pen, x, y + h - 7, x + 6, y + h - 7);
                    break;
            }
        }

        internal void ClearGroups()
        {
            groups.Clear();
        }

        protected override void Dispose(bool disposing)
        {
            if (_ClearBrush != null)
                _ClearBrush.Dispose();

            if (_pen != null)
                _pen.Dispose();

            base.Dispose(disposing);
        }

        public bool IsGroupHeader
        {
            get
            {
                return Groups != null && Groups.Count > 0 && this.Groups[0].IsHeaderItem;
            }
        }
    }
}